﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;

namespace LaoLuo
{
    /// <summary>
    /// 提供IEnumerable&lt;T>的扩展方法
    /// </summary>
    public static class IEnumerableExtensions
    {
        public static T RandomSingle<T>(this IEnumerable<T> source)
        {
            var _source = source.ToArray();
            return _source[RandomProvider.RandomNumber.Next(_source.Length - 1)];
        }

        public static IEnumerable<TSource> Distinct<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            var comparer = new KeyComparer<TSource, TKey>(keySelector);

            return source.Distinct(comparer);
        }


        public static IEnumerable<T> Concat<T>(this IEnumerable<T> source, T item)
        {
            return source.Concat(new[] {item});
        }

        public static IEnumerable<T> Concat<T>(this IEnumerable<T> source, params T[] item)
        {
            return source.Concat(item.AsEnumerable());
        }

        /// <summary>
        /// gets a randomized sequence of <paramref name="source"/>
        /// <para>check http://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more infomation</para>
        /// </summary>
        /// <param name="source"></param>
        /// <param name="rng"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
        {
            var elements = source.ToArray();
            for (var i = elements.Length - 1; i >= 0; i--)
            {
                // Swap element "i" with a random earlier element it (or itself)
                // ... except we don't really need to swap it fully, as we can
                // return it immediately, and afterwards it's irrelevant.
                var swapIndex = rng.Next(i + 1);
                yield return elements[swapIndex];
                elements[swapIndex] = elements[i];
            }
        }


        /*/// <summary>
        /// gets a randomized sequence of <paramref name="source"/>
        /// <para>check http://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more infomation</para>
        /// </summary>
        /// <typeparam name="T">type of elements</typeparam>
        /// <param name="source">sequence to be randomized</param>
        /// <returns>a randomized sequence</returns>
        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            var iList = source as IList<T> ?? source.ToArray();

            var indexes = Enumerable.Range(0, iList.Count).ToArray();
            var generator = new Random();

            for (int i = 0; i < iList.Count; ++i)
            {
                var position = generator.Next(i, iList.Count);
                yield return iList[indexes[position]];
                indexes[position] = indexes[i];
            }
        }*/

        /// <summary>
        /// gets a value that indicates whether all elements in <paramref name="source"/> is the same reference
        /// </summary>
        /// <typeparam name="T">type of elements, must be class</typeparam>
        /// <param name="source">value to test</param>
        /// <returns>if all elements in <paramref name="source"/> is the same reference, returns true, else false</returns>
        public static bool ShareSameReference<T>(this IEnumerable<T> source)
            where T : class
        {
            if (source == null || !source.Any())
            {
                throw new ArgumentException("value is null or does not contain any element");
            }

            var first = source.First();

            foreach (var item in source)
            {
                if (!object.ReferenceEquals(first, item))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 获取指定元素的位置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static int IndexOf<T>(this IEnumerable<T> source, T value)
        {
            return source.IndexOf(value, null);
        }

        /// <summary>
        /// 获取指定元素的位置
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="value"></param>
        /// <param name="comparer"></param>
        /// <returns></returns>
        public static int IndexOf<T>(this IEnumerable<T> source, T value, IEqualityComparer<T> comparer)
        {
            comparer = comparer ?? EqualityComparer<T>.Default;
            var found = source
                .Select((a, i) => new { a, i })
                .FirstOrDefault(x => comparer.Equals(x.a, value));
            return found == null ? -1 : found.i;
        }

        /// <summary>
        /// 返回string.Join(separator, obj)的结果
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        public static string GetJoint<T>(this IEnumerable<T> source, string separator = ",")
        {
            return string.Join(separator, source);
        }

        /// <summary>
        /// 获取包含非默认属性最多的一个元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static T GetSingleWithMostInfo<T>(this IEnumerable<T> source)
        {
            return source.FirstOrDefault(i => i.GetNondefaultPropCount() == source.Max(j => j.GetNondefaultPropCount()));
        }

        /// <summary>
        /// WARNING: infinite loop
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        private static IEnumerable<T> GetCircle<T>(this IEnumerable<T> source)
        {
            while (true)
            {
                foreach (var item in source)
                {
                    yield return item;
                }
            }
        }

        private class KeyComparer<TSource, TKey> : IEqualityComparer<TSource>
        {
            private Func<TSource, TKey> keySelector;

            public KeyComparer(Func<TSource, TKey> keySelector)
            {
                this.keySelector = keySelector;
            }

            /*bool IEqualityComparer.Equals(TSource a, TSource b)
            {
                if (a == null && b == null) return true;
                if (a == null || b == null) return false;

                return keySelector(a) == keySelector(b);
            }

            int IEqualityComparer.GetHashCode(TSource obj)
            {
                return keySelector(obj).GetHashCode();
            }*/
            bool IEqualityComparer<TSource>.Equals(TSource x, TSource y)
            {
                if (x == null && y == null) return true;
                if (x == null || y == null) return false;

                return keySelector(x).Equals(keySelector(y));
            }

            int IEqualityComparer<TSource>.GetHashCode(TSource obj)
            {
                return keySelector(obj).GetHashCode();
            }
        }

    }
}
